#pragma once

#include <ctime>
#include <memory>
#include <cstdint>
#include "../protocol/protocol_register.hh"
#include "../../engine/thirdparty/kconfig/interface/config.h"
#include "../../engine/engine.hh"
#include "../../corelib/object_pool.hpp"
#include "../../corelib/time_util.hh"
#include "../../corelib/timer_queue.hh"
#include "../../corelib/event/event_queue.hh"
#include "../../corelib/event/event.hh"
#include "../../corelib/event/event_listener.hh"
#include "../../corelib/event/event_object.hh"
#include "../../corelib/network/parameter.hh"
#include "../../corelib/util/allocator.hh"

using namespace kratos::engine;
using namespace google::protobuf;

enum class SearchType : uint32_t {
    NONE = 0,
    ROUNDROBIN,
};

struct ProtocolHeader;
struct ProtocolLocatorHeader;

using SearchMethod = std::function<void(std::uint32_t, const EntityIDVector&, std::uint64_t)>;

// module framework interface
class Framework {
private:
    std::unique_ptr<char> sendBuffer_; // send buffer
    // Entity registry
    struct EntityRegistry {
        std::uint64_t entityID; // entity ID
        std::uint32_t entityType; // entity type
        PathID pathID; // locator's path ID
        std::uint32_t retryTimes; // retry times
    };
    static constexpr std::uint32_t MAX_RETRY_TIMES = 5; // The maximum retry times for entity registering
    using EntityRegistryVector = std::vector<EntityRegistry>;
    using RegisterEntityMap = UNORDERED_MAP(std::uint64_t, EntityRegistry, Framework_registeredEntityMap_);
    using PendingEntityMap = UNORDERED_MAP(std::uint64_t, EntityRegistry, Framework_pendingEntityMap_);
    RegisterEntityMap registeredEntityMap_; // already registered
    PendingEntityMap pendingEntityMap_; // waiting for registering

public:
    // invoked by engine when module installed
    // @param config configuration
    // @retval false fail
    // @retval true
    virtual bool onInstall(kconfig::Config* config);
    // invoked by engine when module stopped
    // @retval false fail
    // @retval true
    virtual bool onUninstall();
    // invoked by engine when startup
    // @param config configuration
    // @retval false fail
    // @retval true
    virtual bool onStart(kconfig::Config* config);
    // invoked by engine when cleanup
    // @retval false fail
    // @retval true
    virtual bool onStop();
    // Returns module type
    virtual ModuleType onModuleType();
    // invoked by engine when GM command reached
    // @param command GM command
    // @return result
    virtual std::string onCommand(const std::string & command);
    // invoked by engine each cycle
    // @param millionseconds current timestamp in millionseconds
    virtual void onTick(std::time_t millionseconds);
    // invoked by engine when path connected or accepted
    // @param pathID path ID
    // @param siteType grid type which path connect(accept) to(from)
    // @param userData user offerred user data when call findPath()
    virtual void onPathJoin(PathID pathID, SiteType siteType, UserData userData);
    // invoked by engine when path disconnected
    // @param pathID path ID
    // @param siteType grid type which path connect(accept) to(from)
    virtual void onPathDisjoin(PathID pathID, SiteType siteType);
    // invoked by engine when finding path timed out
    // @param siteType grid type which path connect(accept) to(from)
    // @param userData user defined data
    virtual void onFindPathTimeout(SiteType siteType, UserData userData);
    // invoked by engine when read a protobuf::Message from it
    // @param pathID path ID
    // @param siteType grid type which path connect(accept) to(from)
    // @param msgID message ID
    // @param msg protobuf:Message instance
    virtual void onPathData(PathID pathID, SiteType siteType, std::uint32_t msgID, Message& msg);
    // invoked by engine when read a protobuf::Message from it
    // @param pathID path ID
    // @param siteType grid type which path connect(accept) to(from)
    // @param msgID message ID
    // @param msg protobuf:Message instance
    virtual void onPathData(PathID pathID, SiteType siteType, std::uint32_t msgID,
        const char* data, std::size_t size);
    // invoked by engine when accept a gateway session
    // @param sessionID session ID
    virtual void onSessionJoin(SessionID sessionID);
    // invoked by engine when gateway session disconnected
    // @param sessionID session ID
    virtual void onSessionDisjoin(SessionID sessionID);
    // invoked by engine when read a protobuf::Message from it
    // @param sessionID session ID
    // @param msgID message ID
    // @param msg protobuf:Message instance
    virtual void onSessionData(SessionID sessionID, std::uint32_t msgID, Message& msg);
    // invoked by engine when read a protobuf::Message from it but no message handler matching
    // @param sessionID session ID
    // @param msgID message ID
    // @param data data stream
    // @param size data size in bytes
    virtual void onSessionData(SessionID sessionID, std::uint32_t msgID, const char* data,
        std::size_t size);
    // invoked by engine when timer timeout
    // @param timerID timer ID
    // @param userData user offerred user data when call startTimer() or startTimerOnce()
    virtual void onTimer(TimerHandle timerID, std::uint64_t userData);
    // invoked by engine when grid site has been connected or accepted
    // @param siteID grid site ID
    virtual void onSiteJoin(const kratos::grid::SiteID& siteID);
    // invoked by engine when grid site disconnecting
    // @param siteID grid site ID
    virtual void onSiteDisjoin(const kratos::grid::SiteID& siteID);
    // invoked by engine when received data
    // @param siteID grid site ID
    // @param msgID message ID
    // @param msg The protobuf message
    virtual void onSiteData(const kratos::grid::SiteID& siteID, std::uint32_t msgID, Message& msg);
    // invoked by engine when received data
    // @param siteID grid site ID
    // @param msgID message ID
    // @param data the pointer of received data
    // @param size the length of data
    virtual void onSiteData(const kratos::grid::SiteID& siteID, std::uint32_t msgID, const char* data, std::size_t size);
    // invoked by framework when entity found if no user offered callback
    // @param type entity type
    // @param ids entity IDs
    // @param userData user offered data
    virtual void onEntityFound(std::uint32_t type, const EntityIDVector& ids, std::uint64_t userData);
    // invoked by framework when read a http request from it
    // @param sessionID session ID
    // @param httpProto http message
    virtual void onHttpData(SessionID sessionID, const kratos::util::HttpProto& httpProto);
    virtual void onSessionConnected(SessionID sessionID, std::uint64_t userData);

public:
    // ctor
    Framework();
    // dtor
    virtual ~Framework();
    // start timer
    // @param duration timer period in millionseconds
    // @param userData user data
    // @retval INVALID_TIMER_ID start failed
    TimerHandle startTimer(std::time_t duration, std::uint64_t userData);
    // start timer and stop it when timeout 
    // @param duration timer period in millionseconds
    // @param userData user data
    // @retval INVALID_TIMER_ID start failed
    // @retval timer ID
    TimerHandle startTimerOnce(std::time_t duration, std::uint64_t userData);
    // start timer
    // @param duration timer period in millionseconds
    // @param cb timer callback
    // @param userData user defined data
    // @retval INVALID_TIME_ID fail
    // @retval timer ID
    TimerHandle startTimer(std::time_t duration, TimerCallback cb, std::uint64_t userData = 0);
    // start timer and stop it when timeout 
    // @param duration timer period in millionseconds
    // @param cb timer callback
    // @param userData user defined data
    // @retval INVALID_TIME_ID fail
    // @retval timer ID
    TimerHandle startTimerOnce(std::time_t duration, TimerCallback cb, std::uint64_t userData = 0);
    // cancel timer
    // @param timerID timer ID
    void cancelTimer(TimerHandle timerID);
    // find a path to specified grid type
    // @param siteType grid type
    // @param userData user data
    // @retval true
    // @retval false fail
    bool findPath(SiteType siteType, UserData userData);
    // close path
    // @param pathID path ID
    void closePath(PathID pathID);
    // close gateway session
    // @param sessionID session ID
    void closeSession(SessionID sessionID);
    // send protobuf::Message via path
    // @param pathID path ID
    // @param msg protobuf::Message instance
    // @retval true
    // @retval false fail
    bool talkPath(PathID pathID, const Message& msg);
    // send protobuf::Message via path
    // @param pathID path ID
    // @param data data pointer
    // @param size data length
    // @retval true
    // @retval false fail
    bool talkPath(PathID pathID, const char* data, std::size_t size);
    // send protobuf::Message via gateway session
    // @param sessionID session ID
    // @param msg protobuf::Message instance
    // @retval true
    // @retval false fail
    bool talkSession(SessionID sessionID, const Message& msg);
    // send data via gateway session
    // @param sessionID session ID
    // @param data data pointer
    // @param size data length
    // @retval true
    // @retval false fail
    bool talkSession(SessionID sessionID, const char* data, std::size_t size);
    // send protobuf::Message via direct connection
    // @param siteID grid site ID
    // @param msg protobuf::Message instance
    // @retval true
    // @retval false fail
    bool talkSite(const kratos::grid::SiteID& siteID, const Message& msg);
    // send protobuf::Message via direct connection
    // @param siteID site ID
    // @param data data pointer
    // @param size data length
    // @retval true
    // @retval false fail
    bool talkSite(const kratos::grid::SiteID& siteID, const char* data, std::size_t size);
    // send message to entity
    // @param from source target ID
    // @param to target entity ID
    // @param msg google::protobuf::Message
    // @retval true
    // @retval false fail
    bool talkEntity(std::uint64_t from, std::uint64_t to, const Message& msg);
    // broadcast entity message
    // @param entityType entity type
    // @param msg google::protobuf::Message
    // @param exceptID donot send to exceptID
    void broadcastEntity(std::uint32_t entityType, const Message& msg, std::uint64_t exceptID = 0);
    // send message to entity
    // @param to target entity ID
    // @param msg google::protobuf::Message
    // @retval true
    // @retval false fail
    bool talkEntity(std::uint64_t to, const Message& msg);
    // Register entity
    // @param entityID entity ID
    // @param entityType entity type
    // @retval true
    // @retval false fail
    bool registerEntity(std::uint64_t entityID, std::uint32_t entityType);
    // Un-register entity
    // @param entityID entity ID
    // @retval true
    // @retval false fail
    bool unregisterEntity(std::uint64_t entityID);
    // Searchs entity by type
    // @param entityType entity type
    // @param method result callback
    // @param searchType The searching type
    // @param timeout The timeout for first searching
    // @param userData user data
    // @retval true
    // @retval false fail
    bool searchEntity(std::uint32_t entityType, SearchMethod method, SearchType searchType = SearchType::NONE,
        std::time_t timeout = 5000, std::uint64_t userData = 0);
    // returns new http request
    // @param cb callback
    // @param host remote host
    // @param uri URI
    // @param port remote server port
    // @retval nullptr fail
    // @retval khttp::HttpRequest instance
    khttp::HttpRequest* newHttpRequest(HttpMethod cb, const std::string& host, const std::string& uri, int32_t port = 80);
    // start a new http server
    // @param cb callback
    // @param host The host listening at
    // @param port The port listening at
    // @retval true
    // @retval false fail
    bool newHttpServer(HttpMethod cb, const std::string& host, int32_t port = 80);
    // send command to specific module and wait for result
    std::string doCommand(const std::string& moduleName, const std::string& command);
    // send command to engine and wait for result
    std::string doCommand(const std::string& command);
    // Returns plugin's root path
    const std::string& getRootPath();
    // Connects to remote server
    bool connect(const std::string& host, std::uint64_t userKey, std::time_t timeout);

private:
    // invoked by engine when read a entity message from it
    // @param from source entity ID
    // @param to target entity ID
    // @param msgID message ID
    // @param msg protobuf:Message instance
    void onEntityData(std::uint64_t from, std::uint64_t to, std::uint32_t msgID, Message& msg);
    // Locator's path broken
    // @param pathID locator's path ID
    void onLocatorRehash(PathID pathID);
    // Send message to locator
    bool talkLocator(PathID pathID, std::uint32_t msgID, flatbuffers::FlatBufferBuilder& builder);
    // Send message to locator
    bool talkLocator(PathID pathID, std::uint32_t msgID, flatbuffers::FlatBufferBuilder& builder,
        std::uint32_t userMsgID, const Message& msg);
    // Start registering timer
    // @param to target entity ID
    // @retval true
    // @retval false fail
    bool tryToStartRegisterTimer(std::uint64_t entityID);
    // Finish register
    // @param entityID entity ID
    void onFinishRegister(PathID pathID, std::uint64_t entityID);
    // Builds protocol header
    // @param offset The offset(sendBuffer_)
    // @param msgID message ID
    // @param length The message length include header length
    ProtocolHeader* buildHeaderInSendBuffer(std::size_t offset, std::uint32_t msgID, std::uint32_t length);
    // Builds protocol header
    // @param offset The offset(sendBuffer_)
    // @param msgID message ID
    // @param locatorMsgLength The message length of locator message
    // @param length The message length include header length
    ProtocolLocatorHeader* buildLocatorHeaderInSendBuffer(std::size_t offset, std::uint32_t msgID,
        std::uint32_t locatorMsgLength, std::uint32_t length);

    friend class ModuleFrame;
};
